/**
 * 
 */
package org.o14x.alpha.ui.views.foldertree;

import java.io.File;
import java.util.Iterator;

import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.menus.CommandContributionItem;
import org.eclipse.ui.menus.CommandContributionItemParameter;
import org.o14x.alpha.services.FileServices;
import org.o14x.alpha.ui.controller.UIController;

/**
 * The composite showing the folder tree.
 * 
 * @author Olivier DANGREAUX
 */
public class TreeComposite extends Composite {
	/**
	 * Parent FolderTreeView;
	 */
	private FolderTreeView parentView;

	/**
	 * StackLayout showing only the current folder tree.
	 */
	private StackLayout stackLayout;

	/**
	 * The current TreeViewer.
	 */
	private TreeViewer currentTreeViewer;

	/**
	 * The UIController.
	 */
	private UIController uiController;

	/**
	 * Creates a new instance of TreeComposite.
	 *
	 * @param parent The parent composite.
	 * @param parentView The parent FolderTreeView
	 */
	public TreeComposite(Composite parent, FolderTreeView parentView) {
		super(parent, SWT.NONE);
		this.parentView = parentView;
		stackLayout = new StackLayout();
		this.setLayout(stackLayout);
		uiController = parentView.getUiController();
	}

	/**
	 * Gets a newly created TreeViewer.
	 * 
	 * @return The new TreeViewer.
	 */
	public TreeViewer getNewTreeViewer() {
		TreeViewer folderTreeViewer = new TreeViewer(this, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL);
		folderTreeViewer.setContentProvider(new FolderTreeContentProvider());
		folderTreeViewer.setLabelProvider(parentView.getFolderTreeLabelProvider());
		folderTreeViewer.addFilter(parentView.getFolderTreeFilter());
		folderTreeViewer.addSelectionChangedListener(new FolderTreeSelectionListener(parentView.getUiController()));

		folderTreeViewer.setInput(new FolderTreeInput());

		// drag n drop support
		addDragSupport(folderTreeViewer);
		addDropSupport(folderTreeViewer);
		
		// context menu
		addContextMenu(folderTreeViewer);

		return folderTreeViewer;
	}

	/**
	 * Activates the specifies TreeViewer.
	 */
	public void activateTreeViewer(TreeViewer treeViewer) {
		currentTreeViewer = treeViewer;
		stackLayout.topControl = treeViewer.getControl();
	}

	/**
	 * Selects the specified file in the current folder tree.
	 * 
	 * @param file A file.
	 */
	public void setSelection(File file) {
		StructuredSelection structuredSelection = new StructuredSelection(file);
		currentTreeViewer.setSelection(structuredSelection);
		layout();
	}

	/**
	 * Adds drag support.
	 */
	private void addDragSupport(TreeViewer treeViewer) {
		final TreeViewer finalTreeViewer = treeViewer;

		// defines directory tree as a drag source
		Transfer[] types = new Transfer[] { FileTransfer.getInstance() };
		int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;
		finalTreeViewer.addDragSupport(operations, types, new DragSourceListener() {
			@SuppressWarnings("unchecked")
			@Override
			public void dragStart(DragSourceEvent event) {
				event.doit = true;
				IStructuredSelection selection = (IStructuredSelection) finalTreeViewer.getSelection();
				String[] selectedPaths = new String[selection.size()];
				int i = 0;
				for (Iterator iterator = selection.iterator(); iterator.hasNext() && event.doit;) {
					File selectedFile = (File) iterator.next();
					selectedPaths[i] = selectedFile.getAbsolutePath();
					event.doit = selectedFile.getParentFile() != null;
					i++;
				}
			}

			@SuppressWarnings("unchecked")
			@Override
			public void dragSetData(DragSourceEvent event) {
				IStructuredSelection selection = (IStructuredSelection) finalTreeViewer.getSelection();
				String[] selectedPaths = new String[selection.size()];
				int i = 0;
				for (Iterator iterator = selection.iterator(); iterator.hasNext();) {
					File selectedFile = (File) iterator.next();
					selectedPaths[i] = selectedFile.getAbsolutePath();
					i++;
				}
				event.data = selectedPaths;
			}

			@Override
			public void dragFinished(DragSourceEvent event) {
			}
		});
	}

	/**
	 * Adds file drop support.
	 */
	private void addDropSupport(TreeViewer treeViewer) {
		Transfer[] types = new Transfer[] { FileTransfer.getInstance() };
		int operations = DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_LINK;

		// defines directory tree as a drop target
		DropTarget target = new DropTarget(treeViewer.getTree(), operations);
		target.setTransfer(types);
		target.addDropListener(new DropTargetListener() {
			@Override
			public void dropAccept(DropTargetEvent event) {

			}

			@Override
			public void drop(DropTargetEvent event) {
				// computes the target directory
				File targetDirectory = null;
				TreeItem treeItem = (TreeItem) event.item;
				if (treeItem != null) {
					File targetFile = (File) treeItem.getData();
					if (targetFile != null && targetFile.isDirectory()) {
						// drag over a directory => target directory is this directory
						targetDirectory = targetFile;
					}
				}

				if (targetDirectory != null) {
					// paths of the files to be dropped
					String[] droppedPaths = (String[]) event.data;

					FileServices fileServices = uiController.getServices().getFileServices();

					// files to be dropped
					File[] droppedFiles = fileServices.toFiles(droppedPaths);

					// if there is files to be dropped
					if (droppedFiles.length > 0) {
						// move operation
						if (event.detail == DND.DROP_MOVE) {
							// if dropped files are not from the target directory
							if (!droppedFiles[0].getParentFile().equals(targetDirectory)) {
								// moves files
								fileServices.move(droppedFiles, targetDirectory, true, true);

								// updates display
								uiController.getDomain().getCurrentExplorerTab().navigate(targetDirectory);
								parentView.update();
								uiController.getUi().getFolderTreeView().update();
							}
						}
						// copy operation
						else if (event.detail == DND.DROP_COPY) {
							// if dropped files are from the target directory
							if (droppedFiles[0].getParentFile().equals(targetDirectory)) {
								// modify the name of the files before copy
								fileServices.duplicate(droppedFiles, targetDirectory);

								// updates display
								parentView.update();
								uiController.getUi().getFolderTreeView().update();
							} else {
								// copy files without modifying their names
								fileServices.copy(droppedFiles, targetDirectory, false, true);

								// updates display
								parentView.update();
								uiController.getUi().getFolderTreeView().update();
							}
						}
					}
				}
			}

			@Override
			public void dragOver(DropTargetEvent event) {
				TreeItem treeItem = (TreeItem) event.item;
				int detail = event.detail;
				if (event.detail == DND.DROP_NONE) {
					detail = DND.DROP_DEFAULT;
				}

				// if drag over an item
				if (treeItem != null) {
					File targetFile = (File) treeItem.getData();

					// drop can be done only on a directory
					if (targetFile != null && targetFile.isDirectory()) {
						event.detail = detail;
						event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_EXPAND;
					} else {
						event.detail = DND.DROP_NONE;
					}
				}
				// if drag over the rest of the tree
				else {
					// drop is impossible
					event.detail = DND.DROP_NONE;
				}
			}

			@Override
			public void dragOperationChanged(DropTargetEvent event) {
			}

			@Override
			public void dragLeave(DropTargetEvent event) {
			}

			@Override
			public void dragEnter(DropTargetEvent event) {
			}
		});
	}
	
	/****************************************************************************************************************
	 * CONTEXT MENU
	 ***************************************************************************************************************/

	/**
	 * Adds a context menu to the table
	 */
	private void addContextMenu(final TreeViewer treeViewer) {

		final MenuManager mgr = new MenuManager();
		mgr.setRemoveAllWhenShown(true);

		mgr.addMenuListener(new IMenuListener() {

			/* (non-Javadoc)
			 * @see org.eclipse.jface.action.IMenuListener#menuAboutToShow(org.eclipse.jface.action.IMenuManager)
			 */
			public void menuAboutToShow(IMenuManager manager) {
				IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection();

				boolean emptySelection = selection.isEmpty();
				//boolean multipleSelection = !emptySelection && selection.size() > 1;
				boolean singleElementSelection = !emptySelection && selection.size() == 1;
				boolean singleDirectorySelection = singleElementSelection && ((File) selection.getFirstElement()).isDirectory();

				CommandContributionItemParameter commandContributionItemParameter;
				CommandContributionItem commandContributionItem;
				
				commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.SystemContextMenuCommand", CommandContributionItem.STYLE_PUSH);
				commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
				mgr.add(commandContributionItem);

				mgr.add(new Separator());
				
				if (emptySelection) {
					commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.RefreshCommand", CommandContributionItem.STYLE_PUSH);
					commandContributionItemParameter.icon = ImageDescriptor.createFromImage(new Image(parentView.getSite().getShell().getDisplay(), Thread.currentThread().getContextClassLoader().getResourceAsStream("org/o14x/alpha/ui/images/refresh.gif")));
					commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
					mgr.add(commandContributionItem);
					
					mgr.add(new Separator());
				}
				
				mgr.add(new Separator());

				if (!emptySelection) {
					commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.CutCommand", CommandContributionItem.STYLE_PUSH);
					commandContributionItemParameter.icon = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_CUT);
					commandContributionItemParameter.disabledIcon = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_CUT_DISABLED);
					commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
					mgr.add(commandContributionItem);
				}

				if (!emptySelection) {
					commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.CopyCommand", CommandContributionItem.STYLE_PUSH);
					commandContributionItemParameter.icon = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_COPY);
					commandContributionItemParameter.disabledIcon = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_COPY_DISABLED);
					commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
					mgr.add(commandContributionItem);
				}

				if (emptySelection || singleDirectorySelection) {
					commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.PasteCommand", CommandContributionItem.STYLE_PUSH);
					commandContributionItemParameter.icon = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_PASTE);
					commandContributionItemParameter.disabledIcon = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_PASTE_DISABLED);
					commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
					mgr.add(commandContributionItem);
				}

				mgr.add(new Separator());

				if (!emptySelection) {
					commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.DeleteCommand", CommandContributionItem.STYLE_PUSH);
					commandContributionItemParameter.icon = ImageDescriptor.createFromImage(new Image(parentView.getSite().getShell().getDisplay(), Thread.currentThread().getContextClassLoader().getResourceAsStream("org/o14x/alpha/ui/images/delete.gif")));
					commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
					mgr.add(commandContributionItem);
				}

				if (singleElementSelection) {
					commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.rename.RenameCommand", CommandContributionItem.STYLE_PUSH);
					commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
					mgr.add(commandContributionItem);
				}
				
				if (emptySelection) {
					
					mgr.add(new Separator());
					
					MenuManager newMenu = new MenuManager("New");
										
					commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.newdirectory.NewDirectoryCommand", CommandContributionItem.STYLE_PUSH);
					commandContributionItemParameter.label = "Directory";
					commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
					newMenu.add(commandContributionItem);
					
					commandContributionItemParameter = new CommandContributionItemParameter(parentView.getSite(), null, "org.o14x.alpha.ui.commands.newfile.NewFileCommand", CommandContributionItem.STYLE_PUSH);
					commandContributionItemParameter.label = "File";
					commandContributionItem = new CommandContributionItem(commandContributionItemParameter);
					newMenu.add(commandContributionItem);
					
					mgr.add(newMenu);
				}
			}
		});
		treeViewer.getControl().setMenu(mgr.createContextMenu(treeViewer.getControl()));
	}

	/**
	 * Returns the value of uiController.
	 *
	 * @return The value of uiController.
	 */
	public UIController getUiController() {
		return uiController;
	}

	/**
	 * Sets the value of uiController.
	 *
	 * @param uiController The value of uiController to set.
	 */
	public void setUiController(UIController uiController) {
		this.uiController = uiController;
	}
}
